﻿using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace TimeTravel.XnaLibrary.GamePlay
{
	/// <summary>
	/// A class representing a single unit of a race.
	/// </summary>
	public class Unit
	{
		#region Fields
		private string mName;
		private List<Texture2D> mIcons;
		private Texture2D mActiveIcon;
		private int mGoldCost;
		private int mFoodCost;
		private bool mIsWeakened;
		private int mLevel;
		private int mUpgradeCost;
		private int mPosition;
		private int mSpeed;
		private float mHealth;
		private float mMaxHealth;
		private float mDamage;
		private int mRange;
		private float mCrit;
		private float mDodge;

		public static float BasicUpgradeFactor = 1.4f;
		public static float WeakenedFactor = 0.35f;
		#endregion

		#region Properties

		/// <summary>
		/// Gets the name of the unit.
		/// </summary>
		public string Name
		{
			get { return mName; }
			set { mName = value; }
		}

		/// <summary>
		/// Gets the level of the unit.
		/// </summary>
		public int Level
		{
			get { return mLevel; }
			internal set { mLevel = value; }
		}

		/// <summary>
		/// Gets or sets the icons representing the unit.
		/// </summary>
		public List<Texture2D> Icons
		{
			get { return mIcons; }
			set { mIcons = value; }
		}

		/// <summary>
		/// Gets or sets the currently active icon
		/// </summary>
		public Texture2D ActiveIcon
		{
			get { return mActiveIcon; }
			set { mActiveIcon = value; }
		}

		/// <summary>
		/// Gets the gold cost of the unit.
		/// </summary>
		public int GoldCost
		{
			get { return mGoldCost; }
			internal set
			{
				mGoldCost = value; 
				mUpgradeCost = mGoldCost * (mLevel + 1);
			}
		}

		/// <summary>
		/// Gets the food cost of the unit per turn.
		/// </summary>
		public int FoodCost
		{
			get { return mFoodCost; }
		}

		/// <summary>
		/// Gets a value indicating whether this unit has been weakened.
		/// </summary>
		public bool IsWeakened
		{
			get { return mIsWeakened; }
		}

		/// <summary>
		/// Gets the cost for upgrading this unit to the next level.
		/// </summary>
		public int UpgradeCost
		{
			get { return mUpgradeCost; }
		}

		/// <summary>
		/// Gets the maximum tiles the unit can move in a single turn.
		/// </summary>
		public int Speed
		{
			get { return mSpeed; }
			internal set { mSpeed = value; }
		}

		/// <summary>
		/// Gets the current position of the unit.
		/// </summary>
		public int Position
		{
			get { return mPosition; }
			internal set { mPosition = value; }
		}

		/// <summary>
		/// Gets the current health of the unit.
		/// </summary>
		public float Health
		{
			get { return mHealth; }
			internal set { mHealth = value; }
		}
		
		/// <summary>
		/// Gets the unit`s maximum health.
		/// </summary>
		public float MaxHealth
		{
			get { return mMaxHealth; }
			internal set { mMaxHealth = value; }
		}

		/// <summary>
		/// Gets the damage the unit causes.
		/// </summary>
		public float Damage
		{
			get { return mDamage; }
			internal set { mDamage = value; }
		}

		/// <summary>
		/// Gets the range at which the unit can attack.
		/// </summary>
		public int Range
		{
			get { return mRange; }	
			internal set { mRange = value; }	
		}

		/// <summary>
		/// Gets the chance to strike a critical against an enemy.
		/// </summary>
		public float CriticalChance
		{
			get { return mCrit; }
			internal set { mCrit = value; }
		}

		/// <summary>
		/// Gets the chance to dodge an incoming attack.
		/// </summary>
		public float DodgeChance
		{
			get { return mDodge; }
			internal set { mDodge = value; }
		}

		/// <summary>
		/// Gets a value indicating whether the unit has range of 1.
		/// </summary>
		public bool IsMelee
		{
			get { return mRange == 1; }
		}

		#endregion

		/// <summary>
		/// Returns an empty Unit instance. This constructor is only accessible in TimeTravel.XnaLibrary.
		/// </summary>
		internal Unit()
		{
			mIcons = new List<Texture2D>();
		}

		/// <summary>
		/// Creates a new Unit instance based on the specified unit.
		/// </summary>
		/// <param name="unit">The unit to copy properties from.</param>
		public Unit(Unit unit)
		{
			this.mName = unit.mName;
			this.mIcons = unit.mIcons;
			this.mActiveIcon = unit.mActiveIcon;
			this.mGoldCost = unit.mGoldCost;
			this.mFoodCost = unit.mFoodCost;
			this.mLevel = unit.mLevel;
			this.mPosition = unit.mPosition;
			this.mSpeed = unit.mSpeed;
			this.mHealth = unit.mHealth;
			this.mMaxHealth = unit.mMaxHealth;
			this.mDamage = unit.mDamage;
			this.mRange = unit.mRange;
			this.mDodge = unit.mDodge;
			this.mCrit = unit.mCrit;
		}

		/// <summary>
		/// Upgrades the unit`s statistics. This method is only accessible is only accessible in TimeTravel.XnaLibrary.
		/// </summary>
		internal void Upgrade()
		{
			if (mLevel == 0)
			{
				mLevel = 1;
				return;
			}
			mLevel++;
			int iconIndex = mIcons.Count > Level ? Level - 1 : mIcons.Count - 1;
			mActiveIcon = mIcons[iconIndex];
			float upgradeFactor = BasicUpgradeFactor * Utilities.CalculateRandomFactor();
			mHealth *= upgradeFactor;
			mMaxHealth *= upgradeFactor;
			mDamage *= upgradeFactor;
		}

		internal void CalculateFoodCost()
		{
			mFoodCost = (int) (5* 
				(mMaxHealth * 0.2f + 
				(1 - mHealth / mMaxHealth) * 5 + 
				mDamage * 0.75f + 
				(mCrit + mDodge) * 5));
		}

		internal void Weaken()
		{
			mMaxHealth *= WeakenedFactor;
			mHealth *= WeakenedFactor;
			mDamage *= WeakenedFactor;
			mCrit *= WeakenedFactor;
			mDodge *= WeakenedFactor;
			mIsWeakened = true;
		}

		internal void Strengthen()
		{
			mMaxHealth /= WeakenedFactor;
			mHealth /= WeakenedFactor;
			mDamage /= WeakenedFactor;
			mCrit /= WeakenedFactor;
			mDodge /= WeakenedFactor;
			mIsWeakened = false;
		}

		/// <summary>
		/// Finds the nearest enemyUnit from the specified list of enemies. This method is only accessible is only accessible in TimeTravel.XnaLibrary.
		/// </summary>
		/// <param name="unitList">The list to search in.</param>
		internal Unit FindNearestEnemy(List<Unit> unitList)
		{
			if (unitList.Count == 0)
				throw new ArgumentException("Unit list cant be empty");

			Unit nearest = unitList[0];
			int smallestDistance = Math.Abs(mPosition - nearest.mPosition);
			for (int i = 1; i < unitList.Count; i++)
			{
				if (Math.Abs(mPosition - nearest.mPosition) < smallestDistance)
				{
					nearest = unitList[i];
					smallestDistance = Math.Abs(mPosition - nearest.mPosition);
				}
			}
			return nearest;
		}

		/// <summary>
		/// Attacks the specified enemyUnit. This method is only accessible is only accessible in TimeTravel.XnaLibrary.
		/// </summary>
		/// <param name="enemyUnit">The enemyUnit to attack. </param>
		internal void Attack(Unit enemyUnit)
		{
			float chanceToHit = (float) Player.Rng.NextDouble();
			if (chanceToHit <= enemyUnit.DodgeChance)
			{
				return;
			}

			float chanceToCrit = (float) Player.Rng.NextDouble();
			float damageFactor = Utilities.CalculateRandomFactor();
			if (chanceToHit <= this.CriticalChance)
			{
				damageFactor *= 2;
			}
			enemyUnit.mHealth -= this.mDamage * damageFactor;
		}
		
		/// <summary>
		/// Draws the unit at the specified position, using the specified SpriteBatch. SpriteBatch.Begin must have already been called.
		/// </summary>
		/// <param name="spriteBatch">The SpriteBatch to draw the object with. SpriteBatch.Begin must have already been called.</param>
		/// <param name="position">The position to draw the unit at.</param>
		public void Draw(SpriteBatch spriteBatch, Vector2 position)
		{
			position.X += this.mPosition;
			Color tintingColor = Color.White;
			tintingColor.A = (byte)(mHealth / mMaxHealth * 255);
			spriteBatch.Draw(mActiveIcon, position, tintingColor);
		}

		public override bool Equals(object obj)
		{
			Unit comperand = obj as Unit;
			if (comperand == null)
				return false;
			if (this.Name == comperand.Name && this.Damage == comperand.Damage &&
				this.Position == comperand.Position && this.Level == comperand.Level)
			{
				return true;
			}
			return false;
		}

		public override int GetHashCode()
		{
			return mName.GetHashCode() + mDamage.GetHashCode() + mHealth.GetHashCode() + mMaxHealth.GetHashCode() + mPosition.GetHashCode();
		}

		public override string ToString()
		{
			return string.Format("{0}: Damage: {1} Health: {2} Level: {3}, Position: {4}", mName, mDamage, mHealth, mLevel, mPosition);
		}
	}
}
